/* Copyright (c) 2010, mitsuhisa.net
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *     * Neither the name of the misuhisa.net nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <vector>
#include <new>
#include <cstdlib>
#include <cstring>
#include "StrPool.hpp"
#include "String.hpp"

namespace nyu {

namespace {

std::vector<StaticString*> static_strings_;

}

StrPool::StrPool() : ents_(new Entry*[127]), nents_(127) {
    ::memset(ents_, 0, sizeof(Entry*) * nents_);

    for (std::vector<StaticString*>::iterator it = static_strings_.begin(),
            end = static_strings_.end(); it != end; ++it) {
        add(*it);
    }
}

StrPool::~StrPool() {
    for (size_t i = 0, j = nents_; i < j; i++) {
        delete ents_[i];
    }
    delete[] ents_;
}

void StrPool::initialize() {
    instance_ = new StrPool();
}

void StrPool::finalize() {
    delete instance_;
    instance_ = 0;
}

String* StrPool::get(char* str, int) {
    unsigned int h = String::calcHash(str);
    unsigned int i = h % nents_;
    Entry* ent = ents_[i];
    int depth = 0;

    while (ent && ::strcmp(ent->value->strval(), str) != 0) {
        ent = ent->next;
        ++depth;
    }

    if (ent) {
        if (ent->value->strval() != str)
            ::free(str);
        ent->value->ref();
        return ent->value;
    }

    ent = new Entry(new PooledString(str, h), ents_[i]);
    ents_[i] = ent;

    if (depth > 3)
        rehash();

    ent->value->ref();

    return ent->value;
}

String* StrPool::get(const char* str, int) {
    unsigned int h = String::calcHash(str);
    unsigned int i = h % nents_;
    Entry* ent = ents_[i];
    int depth = 0;

    while (ent && ::strcmp(ent->value->strval(), str) != 0) {
        ent = ent->next;
        ++depth;
    }

    if (ent) {
        ent->value->ref();
        return ent->value;
    }

    ent = new Entry(new PooledString(::strdup(str), h), ents_[i]);
    ents_[i] = ent;

    if (depth > 3)
        rehash();

    ent->value->ref();

    return ent->value;
}

String* StrPool::get(const char* str, int, int) {
    unsigned int h = String::calcHash(str);
    unsigned int i = h % nents_;
    Entry* ent = ents_[i];
    int depth = 0;

    while (ent && ::strcmp(ent->value->strval(), str) != 0) {
        ent = ent->next;
        ++depth;
    }

    if (ent) {
        return ent->value;
    }

    ent = new Entry(new PooledString(::strdup(str), h), ents_[i]);
    ents_[i] = ent;

    if (depth > 3)
        rehash();

    return ent->value;
}

void StrPool::addStatic(StaticString* str) {
    static_strings_.push_back(str);
}

void StrPool::rehash() {
    size_t new_size, i, j;
    Entry **new_ents, *ent, **ents;

    new_size = nents_ * 2 + 1;

    new_ents = new Entry*[new_size];

    ::memset(new_ents, 0, sizeof(Entry*) * new_size);

    ents = ents_;
    for (i = 0, j = nents_; i < j; i++) {
        for (ent = ents[i]; ent;) {
            Entry* tmp = ent;
            unsigned int h = tmp->value->hash() % new_size;
            ent = ent->next;
            tmp->next = new_ents[h];
            new_ents[h] = tmp;
        }
    }

    delete[] ents;

    nents_ = new_size;
    ents_ = new_ents;
}

void StrPool::add(String* str) {
    unsigned int h = str->hash();
    unsigned int i = h % nents_;
    ents_[i] = new Entry(str, ents_[i]);
}

StrPool::Entry::~Entry() {
    value->unref();

    Entry* e = next;
    while (e) {
        Entry* n = e->next;
        e->next = 0;

        delete e;

        e = n;
    }
}

StrPool* StrPool::instance_ = 0;

}
